package ru.ifmo.ctddev.krotkov.bags;

import java.util.*;

public class Bag<T> extends AbstractCollection<T> {

	class BagIterator implements Iterator<T> {

		int version;
		Iterator<ArrayList<T>> curList;
		Iterator<T> curEl;
		Bag<T> obj;
		T value;

		@Override
		public boolean hasNext() throws ConcurrentModificationException {
			if (version < obj.time) {
				throw new ConcurrentModificationException();
			}
			return ((curEl != null) && (curEl.hasNext()))
					|| ((curList != null) && curList.hasNext());
		}

		@Override
		public T next() throws ConcurrentModificationException {
			if (version < obj.time) {
				throw new ConcurrentModificationException();
			}
			if ((curEl == null) || (!curEl.hasNext())) {
				curEl = curList.next().iterator();
			}
			value = curEl.next();
			return value;
		}

		@Override
		public void remove() {
			curEl.remove();
			if (obj.main.get(value).size() == 0) {
				curList.remove();
			}
			obj.size--;
		}

		BagIterator(Bag<T> o, int v) {
			curList = o.main.values().iterator();
			version = o.time;
			obj = o;
		}

	}

	LinkedHashMap<T, ArrayList<T>> main;
	int time;
	int size;

	public Bag(Collection<? extends T> collection) {
		this();
		addAll(collection);
	}

	public Bag() {
		time = 0;
		size = 0;
		main = new LinkedHashMap<T, ArrayList<T>>();
	}

	@Override
	public Iterator<T> iterator() {
		return new BagIterator(this, time);
	}

	@Override
	public int size() {
		return size;
	}

	public boolean add(T e) {
		if (!main.containsKey(e)) {
			main.put(e, new ArrayList<T>());
		}
		main.get(e).add(e);
		size++;
		time++;
		return true;
	}

	@Override
	public boolean remove(Object o) {
		if (!main.containsKey(o)) {
			return false;
		}
		time++;
		main.get(o).remove(0);
		if (main.get(o).size() == 0) {
			main.remove(o);
		}
		size--;
		return true;
	}

	@Override
	public boolean contains(Object o) {
		return main.containsKey(o);
	}

	@Override
	public void clear() {
		size = 0;
		main.clear();
	}
}
